import base64
import hashlib
import json
import os
import shutil
import zipfile
from datetime import datetime
from pathlib import Path
from typing import Any

import customtkinter as ctk
from cryptography.fernet import Fernet
from webdav4.client import Client as WebDAVClient

ctk.set_appearance_mode("dark")
ctk.set_default_color_theme("dark-blue")

# Modern card-style color schemes
DARK_COLORS = {
    "bg_primary": "#1a1a1a",  # Dark background
    "bg_secondary": "#2b2b2b",  # Card background
    "bg_tertiary": "#333333",  # Elevated card background
    "card_hover": "#3a3a3a",  # Card hover state
    "border": "#404040",  # Subtle border
    "shadow": "#000000",  # Card shadow
    "text_primary": "#ffffff",  # Primary text
    "text_secondary": "#b3b3b3",  # Secondary text
    "text_muted": "#666666",  # Muted text
    "accent_primary": "#007acc",  # Primary accent (blue)
    "accent_hover": "#005a9e",  # Primary accent hover
    "accent_red": "#ff6b6b",  # Error/active state
    "accent_red_hover": "#ff5252",  # Error hover
    "success_green": "#4caf50",  # Success state
    "warning_orange": "#ff9800",  # Warning state
}

LIGHT_COLORS = {
    "bg_primary": "#f8f8f6",  # Warm, eye-friendly background
    "bg_secondary": "#fefffe",  # Soft white with warm undertone
    "bg_tertiary": "#f3f4f2",  # Elevated background with subtle contrast
    "card_hover": "#eef1ee",  # Gentle hover effect
    "border": "#d0d7de",  # Professional border color
    "shadow": "#eaeef2",  # Soft shadow color
    "text_primary": "#24292f",  # High contrast primary text
    "text_secondary": "#57606a",  # Well-balanced secondary text
    "text_muted": "#8c959f",  # Properly muted text
    "accent_primary": "#0969da",  # Modern, accessible blue
    "accent_hover": "#0860ca",  # Corresponding hover state
    "accent_red": "#d1242f",  # Professional red for errors/active
    "accent_red_hover": "#cf222e",  # Corresponding hover state
    "success_green": "#1a7f37",  # Professional success green
    "warning_orange": "#d97916",  # Balanced warning orange
}

# Default to dark theme
COLORS = DARK_COLORS


class ConfigEncryption:
    """Handle password encryption/decryption"""

    @staticmethod
    def _get_key() -> bytes:
        """Generate a key from a fixed seed for this machine"""
        seed = "cc-switcher-webdav-key"
        key = hashlib.sha256(seed.encode()).digest()
        return base64.urlsafe_b64encode(key)

    @staticmethod
    def encrypt(password: str) -> str:
        """Encrypt password"""
        try:
            fernet = Fernet(ConfigEncryption._get_key())
            return fernet.encrypt(password.encode()).decode()
        except Exception:
            return password

    @staticmethod
    def decrypt(encrypted: str) -> str:
        """Decrypt password"""
        try:
            fernet = Fernet(ConfigEncryption._get_key())
            return fernet.decrypt(encrypted.encode()).decode()
        except Exception:
            return encrypted


class WebDAVManager:
    """Manage WebDAV operations"""

    def __init__(self, config: dict[str, str]):
        self.config = config
        self.client: WebDAVClient | None = None

    def connect(self) -> bool:
        """Test WebDAV connection"""
        try:
            password = ConfigEncryption.decrypt(self.config.get("password", ""))
            self.client = WebDAVClient(
                base_url=self.config["server_url"],
                auth=(self.config["username"], password),
            )
            # Test connection by listing root
            self.client.ls("/")
            return True
        except Exception:
            return False

    def list_backups(self) -> list[str]:
        """List all backup files on the server"""
        if self.client is None or not self.connect():
            return []
        try:
            remote_dir = self.config.get("remote_dir", "/")
            files: list[dict[str, Any]] = self.client.ls(remote_dir)  # type: ignore
            # Filter for backup zip files - extract basename for matching
            backups = []
            for f in files:
                display_name = f["display_name"]
                backups.append(display_name)
            return backups
        except Exception:
            return []

    def upload_backup(self, local_zip_path: Path) -> bool:
        """Upload backup zip to server"""
        if self.client is None or not self.connect():
            return False
        try:
            remote_dir = self.config.get("remote_dir", "/")
            remote_path = f"{remote_dir}/{local_zip_path.name}"
            self.client.mkdir(remote_dir)
            with open(local_zip_path, "rb") as f:
                self.client.upload_fileobj(f, remote_path)
            return True
        except Exception:
            return False

    def download_backup(self, backup_name: str, local_path: Path) -> bool:
        """Download backup from server"""
        if self.client is None or not self.connect():
            return False
        try:
            remote_dir = self.config.get("remote_dir", "/")
            remote_path = f"{remote_dir}/{backup_name}"
            with open(local_path, "wb") as f:
                self.client.download_fileobj(remote_path, f)
            return True
        except Exception:
            return False

    def delete_backup(self, backup_name: str) -> bool:
        """Delete backup from server"""
        if self.client is None or not self.connect():
            return False
        try:
            remote_dir = self.config.get("remote_dir", "/")
            remote_path = f"{remote_dir}/{backup_name}"
            self.client.remove(remote_path)
            return True
        except Exception:
            return False


class ClaudeConfigSwitcher:
    def __init__(self):
        self.root = ctk.CTk()
        # Configure window properties
        self.root.configure(fg_color=COLORS["bg_primary"])
        self.root.title("cc switcher")

        # Keep system window but make it resizable and with proper taskbar behavior
        self.root.resizable(True, True)

        # Set initial size
        window_width = 900
        window_height = 430

        # Get screen dimensions and center the window
        self.root.update_idletasks()
        screen_width = self.root.winfo_screenwidth()
        screen_height = self.root.winfo_screenheight()
        center_x = int((screen_width - window_width) // 2)
        center_y = int((screen_height - window_height) // 2)
        self.root.geometry(f'{window_width}x{window_height}+{center_x}+{center_y}')

        self.claude_dir = Path.home() / ".claude"
        self.settings_file = self.claude_dir / "settings.json"
        self.app_state_file = self.claude_dir / ".cc-cache"
        self.webdav_config_file = self.claude_dir / "webdav-config.json"

        self.config_files = []
        self.current_config = None
        self.webdav_panel = None  # Will hold the WebDAV panel when shown

        # Initialize theme from saved state
        self.init_theme()

        # Initialize UI after all variables are set
        self.setup_ui()

        # Apply theme colors after UI is created
        if ctk.get_appearance_mode() == "Light":
            self.apply_theme_colors()

        # Use after_idle to ensure UI is ready before refreshing
        self.root.after_idle(lambda: self.refresh_config_list(is_initial=True))

    def init_theme(self):
        """Initialize theme from saved state"""
        global COLORS
        app_state = self.load_app_state()
        saved_theme = app_state.get('theme_mode', 'dark')

        if saved_theme == 'light':
            ctk.set_appearance_mode("light")
            COLORS = LIGHT_COLORS
        else:
            ctk.set_appearance_mode("dark")
            COLORS = DARK_COLORS

    def load_webdav_config(self) -> dict[str, str]:
        """Load WebDAV configuration from file"""
        try:
            if self.webdav_config_file.exists():
                with open(self.webdav_config_file, 'r', encoding='utf-8') as f:
                    return json.load(f)
        except (json.JSONDecodeError, IOError):
            pass
        return {
            "server_url": "",
            "username": "",
            "password": "",
            "remote_dir": "/",
        }

    def save_webdav_config(self, config: dict[str, str]) -> bool:
        """Save WebDAV configuration to file"""
        try:
            self.claude_dir.mkdir(exist_ok=True)
            with open(self.webdav_config_file, 'w', encoding='utf-8') as f:
                json.dump(config, f, indent=2)
            return True
        except (IOError, OSError):
            return False

    def load_app_state(self):
        """Load the last selected file and theme from app state"""
        try:
            if self.app_state_file.exists():
                with open(self.app_state_file, 'r', encoding='utf-8') as f:
                    state = json.load(f)
                    return {
                        'last_selected_file': state.get('last_selected_file'),
                        'theme_mode': state.get('theme_mode', 'dark'),
                    }
        except (json.JSONDecodeError, IOError):
            pass
        return {'last_selected_file': None, 'theme_mode': 'dark'}

    def save_app_state(self, selected_file_name=None, theme_mode=None):
        """Save the current selected file and theme to app state"""
        try:
            self.claude_dir.mkdir(exist_ok=True)

            # Load existing state
            state = {'last_selected_file': None, 'theme_mode': 'dark'}
            if self.app_state_file.exists():
                try:
                    with open(self.app_state_file, 'r', encoding='utf-8') as f:
                        state = json.load(f)
                except (json.JSONDecodeError, IOError):
                    pass

            # Update with new values if provided
            if selected_file_name is not None:
                state['last_selected_file'] = selected_file_name
            if theme_mode is not None:
                state['theme_mode'] = theme_mode

            with open(self.app_state_file, 'w', encoding='utf-8') as f:
                json.dump(state, f, indent=2)
        except (IOError, OSError):
            pass  # Silently ignore save failures

    def setup_ui(self):
        # --- Main Content Area ---
        content_frame = ctk.CTkFrame(self.root, fg_color="transparent")
        content_frame.pack(fill="both", expand=True, padx=3, pady=3)

        # --- Toolbar (leftmost panel) ---
        self.toolbar = ctk.CTkFrame(content_frame, width=30, corner_radius=0, fg_color=COLORS["bg_secondary"])
        self.toolbar.pack(side="left", fill="y", pady=0, padx=(0, 0.5))
        self.toolbar.pack_propagate(False)

        # --- Toolbar Content ---
        toolbar_container = ctk.CTkFrame(self.toolbar, fg_color="transparent")
        toolbar_container.pack(fill="both", expand=True, padx=2, pady=8)

        # Top button container for sync button
        top_container = ctk.CTkFrame(toolbar_container, fg_color="transparent")
        top_container.pack(side="top")

        # WebDAV sync button (sun behind cloud icon) - at the very top
        self.sync_btn = ctk.CTkButton(
            top_container,
            text="🌥",
            command=self.webdav_sync,
            width=26,
            height=26,
            corner_radius=0,
            fg_color="transparent",
            hover_color=COLORS["card_hover"],
            text_color=COLORS["text_primary"],
            font=ctk.CTkFont(family="Segoe UI", size=14),
            border_width=0,
        )
        self.sync_btn.pack(pady=(0, 8))

        # Bottom button container to push buttons to bottom
        button_container = ctk.CTkFrame(toolbar_container, fg_color="transparent")
        button_container.pack(side="bottom")

        # Settings button (gear icon) - at the very bottom
        self.settings_btn = ctk.CTkButton(
            button_container,
            text="⚙",
            command=self.open_settings,
            width=26,
            height=26,
            corner_radius=0,
            fg_color="transparent",
            hover_color=COLORS["card_hover"],
            text_color=COLORS["text_primary"],
            font=ctk.CTkFont(family="Segoe UI", size=14),
            border_width=0,
        )
        self.settings_btn.pack(side="bottom", pady=(4, 0))

        # Theme toggle button (sun/moon icon) - above settings
        initial_theme_icon = "☀" if ctk.get_appearance_mode() == "Light" else "🌙"
        self.theme_btn = ctk.CTkButton(
            button_container,
            text=initial_theme_icon,
            command=self.toggle_theme,
            width=26,
            height=26,
            corner_radius=0,
            fg_color="transparent",
            hover_color=COLORS["card_hover"],
            text_color=COLORS["text_primary"],
            font=ctk.CTkFont(family="Segoe UI", size=14),
            border_width=0,
        )
        self.theme_btn.pack(side="bottom", pady=(0, 4))

        # --- Left Panel ---
        self.left_panel = ctk.CTkFrame(content_frame, width=260, corner_radius=0, fg_color=COLORS["bg_secondary"])
        self.left_panel.pack(side="left", fill="y", pady=0, padx=(1, 1))
        self.left_panel.pack_propagate(False)

        # --- Bottom Controls Container ---
        bottom_container = ctk.CTkFrame(self.left_panel, fg_color="transparent")
        bottom_container.pack(side="bottom", fill="x", padx=8, pady=(4, 8))

        # --- Status Label ---
        self.status_label = ctk.CTkLabel(
            bottom_container,
            text="",
            font=ctk.CTkFont(family="Segoe UI", size=14),
            height=20,
            text_color=COLORS["text_muted"],
        )
        self.status_label.pack(pady=(0, 4), padx=0, fill="x")

        # --- Action Buttons ---
        self.switch_btn = ctk.CTkButton(
            bottom_container,
            text="Switch Config",
            command=self.switch_config,
            height=30,
            font=ctk.CTkFont(family="Segoe UI", size=13, weight="bold"),
            corner_radius=0,
            fg_color=COLORS["bg_tertiary"],
            hover_color=COLORS["card_hover"],
            text_color=COLORS["text_primary"],
            border_width=1,
            border_color=COLORS["border"],
        )
        self.switch_btn.pack(pady=(0, 4), padx=0, fill="x")

        # Button row for secondary actions
        button_row = ctk.CTkFrame(bottom_container, fg_color="transparent")
        button_row.pack(fill="x", pady=0)

        self.refresh_btn = ctk.CTkButton(
            button_row,
            text="Refresh",
            command=self.refresh_config_list,
            height=30,
            corner_radius=0,
            fg_color=COLORS["bg_tertiary"],
            hover_color=COLORS["card_hover"],
            text_color=COLORS["text_primary"],
            font=ctk.CTkFont(family="Segoe UI", size=13),
            border_width=1,
            border_color=COLORS["border"],
        )
        self.refresh_btn.pack(side="left", fill="x", expand=True, padx=(0, 2))

        self.open_dir_btn = ctk.CTkButton(
            button_row,
            text="Open",
            command=self.open_config_directory,
            height=30,
            corner_radius=0,
            fg_color=COLORS["bg_tertiary"],
            hover_color=COLORS["card_hover"],
            text_color=COLORS["text_primary"],
            font=ctk.CTkFont(family="Segoe UI", size=13),
            border_width=1,
            border_color=COLORS["border"],
        )
        self.open_dir_btn.pack(side="right", fill="x", expand=True, padx=(2, 0))

        # --- Config List (takes all remaining space) ---
        list_container = ctk.CTkFrame(self.left_panel, fg_color="transparent")
        list_container.pack(fill="both", expand=True, padx=8, pady=8)

        # Use a regular frame for the config list to avoid always-visible scrollbar
        self.config_listbox = ctk.CTkFrame(list_container, corner_radius=0, fg_color="transparent")
        self.config_listbox.pack(fill="both", expand=True)

        # --- Right Panel (Preview) ---
        self.right_panel = ctk.CTkFrame(content_frame, corner_radius=0, fg_color=COLORS["bg_secondary"])
        self.right_panel.pack(side="left", fill="both", expand=True, padx=(1, 0), pady=0)

        # Preview content
        preview_container = ctk.CTkFrame(self.right_panel, fg_color="transparent")
        preview_container.pack(fill="both", expand=True, padx=8, pady=8)

        self.preview_textbox = ctk.CTkTextbox(
            preview_container,
            corner_radius=0,
            font=ctk.CTkFont(family="Segoe UI", size=13),
            wrap="word",
            fg_color=COLORS["bg_tertiary"],
            text_color=COLORS["text_primary"],
            border_width=1,
            border_color=COLORS["border"],
        )
        self.preview_textbox.pack(fill="both", expand=True)

        self.selected_config = None

    def create_config_button(self, config_file, settings_content):
        # Card container with modern styling
        card = ctk.CTkFrame(
            self.config_listbox,
            height=30,
            corner_radius=0,
            fg_color=COLORS["bg_tertiary"],
            border_width=1,
            border_color=COLORS["border"],
        )
        card.pack(fill="x", pady=(0, 1), padx=0)
        card.pack_propagate(False)

        # Main content frame
        content_frame = ctk.CTkFrame(card, fg_color="transparent")
        content_frame.pack(fill="both", expand=True, padx=8, pady=4)

        # File name with compact typography
        name_label = ctk.CTkLabel(
            content_frame,
            text=config_file.name,
            font=ctk.CTkFont(family="Segoe UI", size=13),
            anchor="w",
            text_color=COLORS["text_primary"],
        )
        name_label.pack(side="left", fill="x", expand=True, anchor="w")

        # Status indicator with modern styling
        is_active = config_file.name == "settings.json"
        is_synced = False

        if not is_active and settings_content is not None:
            try:
                with open(config_file, 'r', encoding='utf-8') as f:
                    current_content = json.load(f)
                if current_content == settings_content:
                    is_synced = True
            except (json.JSONDecodeError, IOError):
                pass

        if is_active:
            status_label = ctk.CTkLabel(
                content_frame,
                text="●",
                font=ctk.CTkFont(family="Segoe UI", size=15, weight="bold"),
                text_color=COLORS["accent_red"],
            )
            status_label.pack(side="right", padx=(6, 0))
        elif is_synced:
            status_label = ctk.CTkLabel(
                content_frame,
                text="●",
                font=ctk.CTkFont(family="Segoe UI", size=15, weight="bold"),
                text_color=COLORS["success_green"],
            )
            status_label.pack(side="right", padx=(6, 0))

        # Add hover effect data
        card._config_file = config_file
        card._is_selected = False

        # Bind click and hover events to all components
        def on_click(e):
            self.select_config(config_file)

        def on_double_click(e):
            self.select_config(config_file)
            self.switch_config()

        def on_enter(e):
            if not card._is_selected:
                card.configure(fg_color=COLORS["card_hover"])

        def on_leave(e):
            if not card._is_selected:
                card.configure(fg_color=COLORS["bg_tertiary"])

        # Bind events to all widgets to prevent flickering
        widgets_to_bind = [card, content_frame, name_label]
        if 'status_label' in locals():
            widgets_to_bind.append(status_label)

        for widget in widgets_to_bind:
            widget.bind("<Button-1>", on_click)
            widget.bind("<Double-Button-1>", on_double_click)
            widget.bind("<Enter>", on_enter)
            widget.bind("<Leave>", on_leave)

    def select_config(self, config_file):
        self.selected_config = config_file

        # Save the selected file to app state
        self.save_app_state(selected_file_name=config_file.name)

        # Update UI selection highlight
        for child in self.config_listbox.winfo_children():
            if hasattr(child, '_config_file'):
                if child._config_file.name == config_file.name:
                    # Selected card - no border for clean look
                    child.configure(fg_color=COLORS["accent_primary"], border_width=0)
                    child._is_selected = True
                    # Update text color for better contrast on blue background
                    for widget in child.winfo_children():
                        if isinstance(widget, ctk.CTkFrame):  # content_frame
                            for label in widget.winfo_children():
                                if isinstance(label, ctk.CTkLabel) and "settings" in label.cget("text"):
                                    label.configure(text_color="white")
                else:
                    # Unselected cards - restore border
                    child.configure(fg_color=COLORS["bg_tertiary"], border_width=1, border_color=COLORS["border"])
                    child._is_selected = False
                    # Restore original text color
                    for widget in child.winfo_children():
                        if isinstance(widget, ctk.CTkFrame):  # content_frame
                            for label in widget.winfo_children():
                                if isinstance(label, ctk.CTkLabel) and "settings" in label.cget("text"):
                                    label.configure(text_color=COLORS["text_primary"])

        self.update_preview(config_file)

    def update_preview(self, config_file):
        try:
            self.preview_textbox.delete("1.0", "end")

            if config_file.exists():
                with open(config_file, 'r', encoding='utf-8') as f:
                    content = f.read()
                try:
                    json_data = json.loads(content)
                    formatted_content = json.dumps(json_data, indent=2, ensure_ascii=False)
                    self.insert_json_with_highlighting(formatted_content)
                except json.JSONDecodeError:
                    self.preview_textbox.insert("1.0", content)

        except Exception:
            self.update_status("Error reading file", COLORS["accent_red"])

    def insert_json_with_highlighting(self, json_content):
        """Insert JSON content with syntax highlighting"""
        import re

        # Define color scheme for JSON syntax highlighting based on current theme
        self.update_json_highlighting_colors()

        # Insert the content
        self.preview_textbox.insert("1.0", json_content)

        # Apply highlighting using regex patterns
        content = json_content

        # Keep track of string positions to avoid re-highlighting them
        string_ranges = []

        # First pass: Highlight strings (including keys and values)
        for match in re.finditer(r'"([^"\\\\]|\\\\.)*"', content):
            start_idx = f"1.0+{match.start()}c"
            end_idx = f"1.0+{match.end()}c"
            string_ranges.append((match.start(), match.end()))

            # Check if this string is a key (followed by colon)
            rest_content = content[match.end() :].lstrip()
            if rest_content.startswith(':'):
                self.preview_textbox.tag_add("key", start_idx, end_idx)
            else:
                self.preview_textbox.tag_add("string", start_idx, end_idx)

        # Helper function to check if position is inside a string
        def is_in_string(pos):
            for start, end in string_ranges:
                if start <= pos < end:
                    return True
            return False

        # Highlight numbers (only outside strings)
        for match in re.finditer(r'-?\d+\.?\d*([eE][+-]?\d+)?', content):
            if not is_in_string(match.start()):
                start_idx = f"1.0+{match.start()}c"
                end_idx = f"1.0+{match.end()}c"
                self.preview_textbox.tag_add("number", start_idx, end_idx)

        # Highlight booleans and null (only outside strings)
        for match in re.finditer(r'\b(true|false|null)\b', content):
            if not is_in_string(match.start()):
                start_idx = f"1.0+{match.start()}c"
                end_idx = f"1.0+{match.end()}c"
                if match.group(1) in ['true', 'false']:
                    self.preview_textbox.tag_add("boolean", start_idx, end_idx)
                else:
                    self.preview_textbox.tag_add("null", start_idx, end_idx)

        # Highlight braces and brackets (only outside strings)
        for match in re.finditer(r'[{}]', content):
            if not is_in_string(match.start()):
                start_idx = f"1.0+{match.start()}c"
                end_idx = f"1.0+{match.end()}c"
                self.preview_textbox.tag_add("brace", start_idx, end_idx)

        for match in re.finditer(r'[\[\]]', content):
            if not is_in_string(match.start()):
                start_idx = f"1.0+{match.start()}c"
                end_idx = f"1.0+{match.end()}c"
                self.preview_textbox.tag_add("bracket", start_idx, end_idx)

        # Highlight colons and commas (only outside strings)
        for match in re.finditer(r':', content):
            if not is_in_string(match.start()):
                start_idx = f"1.0+{match.start()}c"
                end_idx = f"1.0+{match.end()}c"
                self.preview_textbox.tag_add("colon", start_idx, end_idx)

        for match in re.finditer(r',', content):
            if not is_in_string(match.start()):
                start_idx = f"1.0+{match.start()}c"
                end_idx = f"1.0+{match.end()}c"
                self.preview_textbox.tag_add("comma", start_idx, end_idx)

    def switch_config(self):
        if not self.selected_config:
            self.update_status("Please select a config first", COLORS["accent_red"])
            return

        if self.selected_config.name == "settings.json":
            self.update_status("Already the active config", COLORS["text_muted"])
            return

        if not self.selected_config.exists():
            self.update_status("File not found", COLORS["accent_red"])
            return

        try:
            shutil.copy2(self.selected_config, self.settings_file)
            self.update_status(f"Switched to {self.selected_config.name}", COLORS["success_green"])
            self.refresh_config_list()

        except Exception:
            self.update_status("Switch failed", COLORS["accent_red"])

    def open_config_directory(self):
        try:
            os.startfile(self.claude_dir)
        except Exception:
            self.update_status("Failed to open directory", COLORS["accent_red"])

    def update_status(self, message, color=None):
        if color is None:
            color = COLORS["text_muted"]
        self.status_label.configure(text=message, text_color=color)
        # Clear the message after 4 seconds
        self.root.after(4000, lambda: self.status_label.configure(text=""))

    def refresh_config_list(self, is_initial=False):
        try:
            # Remember current selection (only for non-initial refresh)
            current_selection = self.selected_config if not is_initial else None

            # Clear existing list
            for widget in self.config_listbox.winfo_children():
                widget.destroy()

            self.config_files = []
            self.selected_config = None

            if not self.claude_dir.exists():
                self.update_status("Directory not found", COLORS["accent_red"])
                return

            # Get content of settings.json for comparison
            settings_content = None
            if self.settings_file.exists():
                try:
                    with open(self.settings_file, 'r', encoding='utf-8') as f:
                        settings_content = json.load(f)
                except (json.JSONDecodeError, IOError):
                    settings_content = None  # Mark as not readable

            # Scan for settings-related config files and sort them with settings.json on top
            other_files = []
            settings_file_path = None
            for file_path in self.claude_dir.glob("*.json"):
                file_name = file_path.name.lower()
                # Filter to only include settings-related files
                if (
                    file_name == "settings.json"
                    or "settings" in file_name
                    or file_name.startswith("settings_")
                    or file_name.endswith("_settings.json")
                ):
                    if file_path.name == "settings.json":
                        settings_file_path = file_path
                    else:
                        other_files.append(file_path)

            other_files.sort()

            if settings_file_path:
                self.config_files.append(settings_file_path)
            self.config_files.extend(other_files)

            # Create config buttons
            for config_file in self.config_files:
                self.create_config_button(config_file, settings_content)

            if is_initial:
                # Initial load: Restore last selection or default to settings.json
                app_state = self.load_app_state()
                last_selected = app_state.get('last_selected_file')
                target_file = None

                # Try to find the last selected file
                if last_selected:
                    for config_file in self.config_files:
                        if config_file.name == last_selected:
                            target_file = config_file
                            break

                # If no last selection or file not found, default to settings.json
                if not target_file and settings_file_path:
                    target_file = settings_file_path

                # Select the target file if found
                if target_file:
                    self.select_config(target_file)
            else:
                # Regular refresh: Restore selection if the file still exists
                if current_selection and current_selection.exists():
                    # Find the corresponding file in the new list
                    for config_file in self.config_files:
                        if config_file.name == current_selection.name:
                            self.select_config(config_file)
                            break

        except Exception as e:
            self.update_status(f"Error loading configs: {str(e)}", COLORS["accent_red"])

    def run(self):
        self.root.mainloop()

    def open_settings(self):
        """Open settings dialog or configuration"""
        self.update_status("Settings feature coming soon", COLORS["text_muted"])

    def toggle_theme(self):
        """Toggle between light and dark theme"""
        global COLORS
        current_mode = ctk.get_appearance_mode()
        if current_mode == "Dark":
            ctk.set_appearance_mode("light")
            COLORS = LIGHT_COLORS
            self.theme_btn.configure(text="☀")
            self.save_app_state(theme_mode="light")
            self.update_status("Switched to light theme", COLORS["success_green"])
        else:
            ctk.set_appearance_mode("dark")
            COLORS = DARK_COLORS
            self.theme_btn.configure(text="🌙")
            self.save_app_state(theme_mode="dark")
            self.update_status("Switched to dark theme", COLORS["success_green"])

        # Refresh the UI with new colors
        self.apply_theme_colors()

    def apply_theme_colors(self):
        """Apply current theme colors to all UI components"""
        # Update main window
        self.root.configure(fg_color=COLORS["bg_primary"])

        # Update toolbar
        self.toolbar.configure(fg_color=COLORS["bg_secondary"])

        # Update toolbar buttons
        toolbar_buttons = [self.sync_btn, self.theme_btn, self.settings_btn]
        for btn in toolbar_buttons:
            btn.configure(hover_color=COLORS["card_hover"], text_color=COLORS["text_primary"])

        # Update left panel
        self.left_panel.configure(fg_color=COLORS["bg_secondary"])

        # Update status label
        self.status_label.configure(text_color=COLORS["text_muted"])

        # Update action buttons
        action_buttons = [self.switch_btn, self.refresh_btn, self.open_dir_btn]
        for btn in action_buttons:
            btn.configure(
                fg_color=COLORS["bg_tertiary"],
                hover_color=COLORS["card_hover"],
                text_color=COLORS["text_primary"],
                border_color=COLORS["border"],
            )

        # Update right panel
        self.right_panel.configure(fg_color=COLORS["bg_secondary"])

        # Update preview textbox
        self.preview_textbox.configure(
            fg_color=COLORS["bg_tertiary"], text_color=COLORS["text_primary"], border_color=COLORS["border"]
        )

        # Update JSON syntax highlighting colors for current theme
        if hasattr(self, 'preview_textbox'):
            self.update_json_highlighting_colors()

        # Refresh config list to apply new colors
        self.refresh_config_list()

    def update_json_highlighting_colors(self):
        """Update JSON syntax highlighting colors based on current theme"""
        if ctk.get_appearance_mode() == "Light":
            # Light theme colors
            colors = {
                "string": "#d14",  # Red for strings
                "number": "#099",  # Teal for numbers
                "boolean": "#0086b3",  # Blue for booleans
                "null": "#0086b3",  # Blue for null
                "key": "#0086b3",  # Blue for keys
                "brace": "#333",  # Dark gray for braces
                "bracket": "#333",  # Dark gray for brackets
                "colon": "#333",  # Dark gray for colons
                "comma": "#333",  # Dark gray for commas
            }
        else:
            # Dark theme colors (original)
            colors = {
                "string": "#ce9178",  # Orange for strings
                "number": "#b5cea8",  # Light green for numbers
                "boolean": "#569cd6",  # Blue for booleans
                "null": "#569cd6",  # Blue for null
                "key": "#9cdcfe",  # Light blue for keys
                "brace": "#ffd700",  # Gold for braces
                "bracket": "#ffd700",  # Gold for brackets
                "colon": "#ffffff",  # White for colons
                "comma": "#ffffff",  # White for commas
            }

        # Configure text tags for highlighting
        for tag, color in colors.items():
            self.preview_textbox.tag_config(tag, foreground=color)

    def webdav_sync(self):
        """Show WebDAV configuration and sync panel"""
        # Hide main panels
        self.left_panel.pack_forget()
        self.right_panel.pack_forget()

        # Create WebDAV panel if it doesn't exist
        if self.webdav_panel:
            self.webdav_panel.destroy()

        self.webdav_panel = ctk.CTkFrame(
            self.root.winfo_children()[0],  # content_frame
            corner_radius=0,
            fg_color=COLORS["bg_secondary"],
        )
        self.webdav_panel.pack(side="left", fill="both", expand=True, padx=(1, 0), pady=0)

        # Container with padding
        container = ctk.CTkFrame(self.webdav_panel, fg_color="transparent")
        container.pack(fill="both", expand=True, padx=20, pady=20)

        # Title row with back button
        title_row = ctk.CTkFrame(container, fg_color="transparent")
        title_row.pack(fill="x", pady=(0, 20))

        # Back button
        back_btn = ctk.CTkButton(
            title_row,
            text="←",
            command=self.hide_webdav_panel,
            width=35,
            height=35,
            corner_radius=0,
            fg_color="transparent",
            hover_color=COLORS["card_hover"],
            text_color=COLORS["text_primary"],
            font=ctk.CTkFont(family="Segoe UI", size=18),
            border_width=0,
        )
        back_btn.pack(side="left", padx=(0, 10))

        # Title (centered in remaining space)
        title = ctk.CTkLabel(
            title_row,
            text="WebDAV 同步配置",
            font=ctk.CTkFont(family="Segoe UI", size=18, weight="bold"),
            text_color=COLORS["text_primary"],
            anchor="center",
        )
        title.pack(side="left", fill="x", expand=True)

        # Load existing config
        config = self.load_webdav_config()

        # Server URL row
        url_row = ctk.CTkFrame(container, fg_color="transparent")
        url_row.pack(fill="x", pady=(0, 10))

        url_label = ctk.CTkLabel(
            url_row,
            text="WebDAV 地址",
            font=ctk.CTkFont(family="Segoe UI", size=13),
            text_color=COLORS["text_primary"],
            anchor="w",
            width=100,
        )
        url_label.pack(side="left", padx=(0, 10))

        self.webdav_url_entry = ctk.CTkEntry(
            url_row,
            height=35,
            font=ctk.CTkFont(family="Segoe UI", size=13),
            fg_color=COLORS["bg_tertiary"],
            text_color=COLORS["text_primary"],
            border_color=COLORS["border"],
        )
        self.webdav_url_entry.pack(side="left", fill="x", expand=True)
        self.webdav_url_entry.insert(0, config.get("server_url", ""))

        # Username row
        user_row = ctk.CTkFrame(container, fg_color="transparent")
        user_row.pack(fill="x", pady=(0, 10))

        user_label = ctk.CTkLabel(
            user_row,
            text="用户名",
            font=ctk.CTkFont(family="Segoe UI", size=13),
            text_color=COLORS["text_primary"],
            anchor="w",
            width=100,
        )
        user_label.pack(side="left", padx=(0, 10))

        self.webdav_user_entry = ctk.CTkEntry(
            user_row,
            height=35,
            font=ctk.CTkFont(family="Segoe UI", size=13),
            fg_color=COLORS["bg_tertiary"],
            text_color=COLORS["text_primary"],
            border_color=COLORS["border"],
        )
        self.webdav_user_entry.pack(side="left", fill="x", expand=True)
        self.webdav_user_entry.insert(0, config.get("username", ""))

        # Password row
        pass_row = ctk.CTkFrame(container, fg_color="transparent")
        pass_row.pack(fill="x", pady=(0, 10))

        pass_label = ctk.CTkLabel(
            pass_row,
            text="密码",
            font=ctk.CTkFont(family="Segoe UI", size=13),
            text_color=COLORS["text_primary"],
            anchor="w",
            width=100,
        )
        pass_label.pack(side="left", padx=(0, 10))

        self.webdav_pass_entry = ctk.CTkEntry(
            pass_row,
            height=35,
            show="•",
            font=ctk.CTkFont(family="Segoe UI", size=13),
            fg_color=COLORS["bg_tertiary"],
            text_color=COLORS["text_primary"],
            border_color=COLORS["border"],
        )
        self.webdav_pass_entry.pack(side="left", fill="x", expand=True)
        # Decrypt password for display
        encrypted_pass = config.get("password", "")
        if encrypted_pass:
            self.webdav_pass_entry.insert(0, ConfigEncryption.decrypt(encrypted_pass))

        # Remote directory row
        dir_row = ctk.CTkFrame(container, fg_color="transparent")
        dir_row.pack(fill="x", pady=(0, 20))

        dir_label = ctk.CTkLabel(
            dir_row,
            text="远程目录",
            font=ctk.CTkFont(family="Segoe UI", size=13),
            text_color=COLORS["text_primary"],
            anchor="w",
            width=100,
        )
        dir_label.pack(side="left", padx=(0, 10))

        self.webdav_dir_entry = ctk.CTkEntry(
            dir_row,
            height=35,
            font=ctk.CTkFont(family="Segoe UI", size=13),
            fg_color=COLORS["bg_tertiary"],
            text_color=COLORS["text_primary"],
            border_color=COLORS["border"],
        )
        self.webdav_dir_entry.pack(side="left", fill="x", expand=True)
        self.webdav_dir_entry.insert(0, config.get("remote_dir", "/"))

        # Buttons row
        button_row = ctk.CTkFrame(container, fg_color="transparent")
        button_row.pack(fill="x", pady=(10, 0))

        backup_btn = ctk.CTkButton(
            button_row,
            text="备份",
            command=self.perform_backup,
            height=40,
            font=ctk.CTkFont(family="Segoe UI", size=14, weight="bold"),
            corner_radius=0,
            fg_color=COLORS["accent_primary"],
            hover_color=COLORS["accent_hover"],
            text_color="white",
        )
        backup_btn.pack(side="left", fill="x", expand=True, padx=(0, 5))

        restore_btn = ctk.CTkButton(
            button_row,
            text="恢复",
            command=self.show_restore_dialog,
            height=40,
            font=ctk.CTkFont(family="Segoe UI", size=14, weight="bold"),
            corner_radius=0,
            fg_color=COLORS["success_green"],
            hover_color=COLORS["success_green"],
            text_color="white",
        )
        restore_btn.pack(side="left", fill="x", expand=True, padx=(5, 0))

    def hide_webdav_panel(self):
        """Hide WebDAV panel and show main panels"""
        if self.webdav_panel:
            self.webdav_panel.destroy()
            self.webdav_panel = None

        # Show main panels
        self.left_panel.pack(side="left", fill="y", pady=0, padx=(1, 1))
        self.right_panel.pack(side="left", fill="both", expand=True, padx=(1, 0), pady=0)
        self.refresh_config_list()

    def perform_backup(self):
        """Backup all config files to WebDAV server"""
        # Save current WebDAV config
        config = {
            "server_url": self.webdav_url_entry.get().strip(),
            "username": self.webdav_user_entry.get().strip(),
            "password": ConfigEncryption.encrypt(self.webdav_pass_entry.get()),
            "remote_dir": self.webdav_dir_entry.get().strip() or "/",
        }

        # Validate config
        if not config["server_url"] or not config["username"] or not self.webdav_pass_entry.get():
            self.update_status("请填写完整的 WebDAV 配置", COLORS["accent_red"])
            return

        # Save config
        if not self.save_webdav_config(config):
            self.update_status("保存配置失败", COLORS["accent_red"])
            return

        # Create WebDAV manager
        manager = WebDAVManager(config)

        # Test connection
        self.update_status("正在连接 WebDAV 服务器...", COLORS["text_muted"])
        self.root.update()

        if not manager.connect():
            self.update_status("连接失败，请检查配置", COLORS["accent_red"])
            return

        # Scan for config files to backup (don't rely on self.config_files which may be empty)
        config_files_to_backup: list[Path] = []
        if not self.claude_dir.exists():
            self.update_status("配置目录不存在", COLORS["accent_red"])
            return

        for file_path in self.claude_dir.glob("*.json"):
            file_name = file_path.name.lower()
            # Filter to only include settings-related files
            if (
                file_name == "settings.json"
                or (file_name.startswith("settings") and file_name.endswith(".json"))
                or file_name.endswith("settings.json")
            ):
                config_files_to_backup.append(file_path)

        if not config_files_to_backup:
            self.update_status("没有可备份的配置文件", COLORS["text_muted"])
            return

        # Create backup zip file
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        backup_name = f"backup_{timestamp}.zip"
        temp_zip = self.claude_dir / backup_name

        try:
            self.update_status("正在打包配置文件...", COLORS["text_muted"])
            self.root.update()

            with zipfile.ZipFile(temp_zip, 'w', zipfile.ZIP_DEFLATED) as zipf:
                for config_file in config_files_to_backup:
                    if config_file.exists():
                        # Add file to zip with just the filename (no path)
                        zipf.write(config_file, config_file.name)

            self.update_status("正在上传备份...", COLORS["text_muted"])
            self.root.update()

            if manager.upload_backup(temp_zip):
                # Remove temp file
                temp_zip.unlink()
                self.update_status(f"备份成功: {backup_name}", COLORS["success_green"])
                self.show_success_dialog("备份成功", f"配置文件已成功备份到 WebDAV 服务器\n\n备份文件: {backup_name}")
            else:
                temp_zip.unlink()
                self.update_status("上传失败", COLORS["accent_red"])

        except Exception as e:
            if temp_zip.exists():
                temp_zip.unlink()
            self.update_status(f"备份失败: {str(e)}", COLORS["accent_red"])

    def show_success_dialog(self, title: str, message: str):
        """Show success dialog with message"""
        # Create success dialog window
        success_window = ctk.CTkToplevel(self.root)
        success_window.title(title)
        success_window.configure(fg_color=COLORS["bg_primary"])

        # Set window size and position
        window_width = 400
        window_height = 200
        screen_width = success_window.winfo_screenwidth()
        screen_height = success_window.winfo_screenheight()
        center_x = int((screen_width - window_width) // 2)
        center_y = int((screen_height - window_height) // 2)
        success_window.geometry(f'{window_width}x{window_height}+{center_x}+{center_y}')

        # Make it modal
        success_window.transient(self.root)
        success_window.grab_set()

        # Container
        container = ctk.CTkFrame(success_window, fg_color="transparent")
        container.pack(fill="both", expand=True, padx=30, pady=30)

        # Success icon
        icon_label = ctk.CTkLabel(
            container,
            text="✅",
            font=ctk.CTkFont(family="Segoe UI", size=48),
            text_color=COLORS["success_green"],
        )
        icon_label.pack(pady=(0, 15))

        # Title
        title_label = ctk.CTkLabel(
            container,
            text=title,
            font=ctk.CTkFont(family="Segoe UI", size=18, weight="bold"),
            text_color=COLORS["text_primary"],
        )
        title_label.pack(pady=(0, 10))

        # Message
        message_label = ctk.CTkLabel(
            container,
            text=message,
            font=ctk.CTkFont(family="Segoe UI", size=13),
            text_color=COLORS["text_secondary"],
            wraplength=340,
        )
        message_label.pack(pady=(0, 20))

        # OK button
        ok_btn = ctk.CTkButton(
            container,
            text="确定",
            command=success_window.destroy,
            height=40,
            font=ctk.CTkFont(family="Segoe UI", size=14, weight="bold"),
            corner_radius=0,
            fg_color=COLORS["accent_primary"],
            hover_color=COLORS["accent_hover"],
            text_color="white",
        )
        ok_btn.pack(fill="x")

        # Auto-close after 3 seconds
        success_window.after(3000, success_window.destroy)

    def show_restore_dialog(self):
        """Show restore dialog with backup list"""
        # Get WebDAV config
        config = {
            "server_url": self.webdav_url_entry.get().strip(),
            "username": self.webdav_user_entry.get().strip(),
            "password": ConfigEncryption.encrypt(self.webdav_pass_entry.get()),
            "remote_dir": self.webdav_dir_entry.get().strip() or "/",
        }

        # Validate config
        if not config["server_url"] or not config["username"] or not self.webdav_pass_entry.get():
            self.update_status("请先填写 WebDAV 配置", COLORS["accent_red"])
            return

        # Create restore dialog window
        restore_window = ctk.CTkToplevel(self.root)
        restore_window.title("恢复备份")
        restore_window.configure(fg_color=COLORS["bg_primary"])

        # Set window size and position
        window_width = 600
        window_height = 400
        screen_width = restore_window.winfo_screenwidth()
        screen_height = restore_window.winfo_screenheight()
        center_x = int((screen_width - window_width) // 2)
        center_y = int((screen_height - window_height) // 2)
        restore_window.geometry(f'{window_width}x{window_height}+{center_x}+{center_y}')

        # Make it modal
        restore_window.transient(self.root)
        restore_window.grab_set()

        # Container
        container = ctk.CTkFrame(restore_window, fg_color="transparent")
        container.pack(fill="both", expand=True, padx=20, pady=20)

        # Title
        title = ctk.CTkLabel(
            container,
            text="选择要恢复的备份",
            font=ctk.CTkFont(family="Segoe UI", size=16, weight="bold"),
            text_color=COLORS["text_primary"],
        )
        title.pack(pady=(0, 15))

        # Status label
        status_label = ctk.CTkLabel(
            container,
            text="正在加载备份列表...",
            font=ctk.CTkFont(family="Segoe UI", size=13),
            text_color=COLORS["text_muted"],
        )
        status_label.pack(pady=(0, 10))

        # Backup list container
        list_container = ctk.CTkScrollableFrame(
            container,
            corner_radius=0,
            fg_color=COLORS["bg_secondary"],
            border_width=1,
            border_color=COLORS["border"],
        )
        list_container.pack(fill="both", expand=True, pady=(0, 15))

        # Close button
        close_btn = ctk.CTkButton(
            container,
            text="关闭",
            command=restore_window.destroy,
            height=35,
            font=ctk.CTkFont(family="Segoe UI", size=13),
            corner_radius=0,
            fg_color=COLORS["bg_tertiary"],
            hover_color=COLORS["card_hover"],
            text_color=COLORS["text_primary"],
            border_width=1,
            border_color=COLORS["border"],
        )
        close_btn.pack(fill="x")

        # Load backups in background
        def load_backups():
            manager = WebDAVManager(config)
            if not manager.connect():
                status_label.configure(text="连接失败", text_color=COLORS["accent_red"])
                return

            backups = manager.list_backups()
            if not backups:
                status_label.configure(text="没有找到备份文件", text_color=COLORS["text_muted"])
                return

            status_label.configure(text=f"找到 {len(backups)} 个备份", text_color=COLORS["success_green"])

            # Display backups
            for backup in sorted(backups, reverse=True):  # Show newest first
                self.create_backup_item(list_container, backup, manager, restore_window)

        # Run loading in next event loop iteration
        restore_window.after(100, load_backups)

    def create_backup_item(
        self, container: ctk.CTkScrollableFrame, backup_name: str, manager: WebDAVManager, dialog_window
    ):
        """Create a backup item widget"""
        item_frame = ctk.CTkFrame(
            container,
            height=40,
            corner_radius=0,
            fg_color=COLORS["bg_tertiary"],
            border_width=1,
            border_color=COLORS["border"],
        )
        item_frame.pack(fill="x", pady=(0, 1), padx=0)
        item_frame.pack_propagate(False)

        # Content
        content = ctk.CTkFrame(item_frame, fg_color="transparent")
        content.pack(fill="both", expand=True, padx=10, pady=5)

        # Backup name
        name_label = ctk.CTkLabel(
            content,
            text=backup_name,
            font=ctk.CTkFont(family="Segoe UI", size=13),
            text_color=COLORS["text_primary"],
            anchor="w",
        )
        name_label.pack(side="left", fill="x", expand=True)

        # Buttons
        button_frame = ctk.CTkFrame(content, fg_color="transparent")
        button_frame.pack(side="right")

        # Download button (square)
        download_btn = ctk.CTkButton(
            button_frame,
            text="📥",
            command=lambda: self.perform_restore(backup_name, manager, dialog_window),
            width=28,
            height=28,
            corner_radius=0,
            fg_color=COLORS["success_green"],
            hover_color=COLORS["success_green"],
            text_color="white",
            font=ctk.CTkFont(family="Segoe UI", size=13),
        )
        download_btn.pack(side="left", padx=(0, 5))

        # Delete button (square) - fixed width to match download button
        delete_btn = ctk.CTkButton(
            button_frame,
            text="🗑",
            command=lambda: self.delete_backup(backup_name, manager, item_frame),
            width=28,
            height=28,
            corner_radius=0,
            fg_color=COLORS["accent_red"],
            hover_color=COLORS["accent_red_hover"],
            text_color="white",
            font=ctk.CTkFont(family="Segoe UI", size=13),
        )
        delete_btn.pack(side="left")

    def delete_backup(self, backup_name: str, manager: WebDAVManager, item_widget):
        """Delete a backup from server"""
        if manager.delete_backup(backup_name):
            item_widget.destroy()
            self.update_status(f"已删除: {backup_name}", COLORS["success_green"])
        else:
            self.update_status("删除失败", COLORS["accent_red"])

    def perform_restore(self, backup_name: str, manager: WebDAVManager, dialog_window):
        """Restore backup from server"""
        self.update_status("正在下载备份...", COLORS["text_muted"])
        self.root.update()

        # Download backup
        temp_zip = self.claude_dir / backup_name

        if not manager.download_backup(backup_name, temp_zip):
            self.update_status("下载失败", COLORS["accent_red"])
            return

        try:
            self.update_status("正在删除本地配置文件...", COLORS["text_muted"])
            self.root.update()

            # Delete all settings*.json files in ~/.claude
            for file_path in self.claude_dir.glob("*.json"):
                file_name = file_path.name.lower()
                if (
                    file_name == "settings.json"
                    or (file_name.startswith("settings") and file_name.endswith(".json"))
                    or file_name.endswith("settings.json")
                ):
                    try:
                        file_path.unlink()
                    except Exception:
                        pass

            self.update_status("正在解压备份...", COLORS["text_muted"])
            self.root.update()

            # Extract backup
            with zipfile.ZipFile(temp_zip, 'r') as zipf:
                zipf.extractall(self.claude_dir)

            # Remove temp file
            temp_zip.unlink()

            self.update_status(f"恢复成功: {backup_name}", COLORS["success_green"])

            # Close dialog and refresh
            dialog_window.destroy()
            self.hide_webdav_panel()

        except Exception as e:
            if temp_zip.exists():
                temp_zip.unlink()
            self.update_status(f"恢复失败: {str(e)}", COLORS["accent_red"])


def main():
    app = ClaudeConfigSwitcher()
    app.run()


if __name__ == "__main__":
    main()
